package actor.persistencefsm

/**
 * Created by DJJ on 2020/1/3.
 */
package persistentfsm.cart
import WebShopping._
import akka.persistence.fsm._
import akka.persistence.fsm.PersistentFSM._
import akka.persistence._
import akka.actor._
import scala.concurrent.duration._
import scala.reflect._

object WebShopping {
  sealed trait UserState extends FSMState  //状态类型
  case object LookingAround extends UserState {  //浏览状态，可转Shopping状态
    override def identifier: String = "Looking Around"
  }
  case object Shopping extends UserState {  //拣选状态，可转到Paid状态或超时变Inactive
    override def identifier: String = "Shopping"
  }
  case object Inactive extends UserState {  //停滞状态，可转回Shopping状态
    override def identifier: String = "Inactive"
  }
  case object Paid extends UserState {   //结账完成购物，只能查询购物结果，或退出
    override def identifier: String = "Paid"
  }

  case class Item(id: String, name: String, price: Double)
  //state data
  sealed trait ShoppingCart {  //true functional structure
    def addItem(item: Item): ShoppingCart
    def removeItem(id: String): ShoppingCart
    def empty(): ShoppingCart
  }
  case class LoadedCart(items: Seq[Item]) extends ShoppingCart {
    override def addItem(item: Item): ShoppingCart = LoadedCart(items :+ item)
    override def removeItem(id: String): ShoppingCart = {
      val newItems = items.filter {item => item.id != id}
      if (newItems.length > 0)
        LoadedCart(newItems)
      else
        EmptyCart
    }
    override def empty() = EmptyCart
  }
  case object EmptyCart extends ShoppingCart {
    override def addItem(item: Item) = LoadedCart(item :: Nil)
    override def empty() = this
    override def removeItem(id: String): ShoppingCart = this
  }

  sealed trait Command
  case class AddItem(item: Item) extends Command
  case class RemoveItem(id: String) extends Command
  case object Buy extends Command
  case object Leave extends Command
  case object GetCart extends Command

  sealed trait DomainEvent
  case class ItemAdded(item: Item) extends DomainEvent
  case class ItemRemoved(id: String) extends DomainEvent
  case object OrderClosed extends DomainEvent
  //logging message type
  case class CurrentCart(location: String, cart: ShoppingCart)

  def props(uid: String) = Props(new WebShopping(uid))

}
class WebShopping(webUserId: String) extends PersistentFSM[UserState,ShoppingCart,DomainEvent] {
  override def persistenceId: String = webUserId
  override def domainEventClassTag: ClassTag[DomainEvent] = classTag[DomainEvent]

  override def applyEvent(event: DomainEvent, currentCart: ShoppingCart): ShoppingCart =
    event match {
      case ItemAdded(item) => currentCart.addItem(item)
      case ItemRemoved(id) => currentCart.removeItem(id)
      case OrderClosed => currentCart.empty()  //买单成功后清空ShoppingCart
    }

  startWith(LookingAround,EmptyCart)  //初次登陆购物状态

  when(LookingAround) {   //浏览时可以加入购物车转到Shopping状态
    case Event(AddItem(item),currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"LookingAround-Adding Item: $item",currentCart))
      goto(Shopping) applying ItemAdded(item) forMax(1 second)
    case Event(GetCart,currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"LookingAround-Showing",currentCart))
      stay replying currentCart
  }

  when(Shopping) {
    case Event(AddItem(item),currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Shopping-Adding Item: $item",currentCart))
      stay applying ItemAdded(item) forMax (1 second) andThen {
        case cart @ _ =>
          context.system.eventStream.publish(CurrentCart(s"Shopping-after adding Item: $item",cart))
      }
    case Event(RemoveItem(id),currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Shopping-Removing Item: $id",currentCart))
      stay applying ItemRemoved(id) forMax (1 second) andThen {
        case cart @ _ =>
          context.system.eventStream.publish(CurrentCart(s"Shopping-after removing Item: $id",cart))
      }
    case Event(Buy,currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Shopping-Buying",currentCart))
      goto(Paid) applying OrderClosed forMax (1 second) andThen {
        case cart @ _ => saveStateSnapshot()
          context.system.eventStream.publish(CurrentCart(s"Shopping-after paid",cart))
      }

    case Event(Leave,currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Shopping-Leaving",currentCart))
      stop()
    case Event(StateTimeout,currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Shopping-Timeout",currentCart))
      goto(Inactive) forMax(1 second)
    case Event(GetCart,currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"LookingAround-Showing",currentCart))
      stay replying currentCart
  }

  when(Inactive) {
    case Event(AddItem(item),currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Inactive-Adding Item: $item",currentCart))
      goto(Shopping) applying ItemAdded(item) forMax(1 second)
    case Event(StateTimeout,currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Inactive-Timeout",currentCart))
      stop()
  }

  when(Paid) {
    case Event(Leave,currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Paid-Leaving",currentCart))
      stop()
    case Event(GetCart,currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Paid-Showing",currentCart))
      stay replying currentCart
    case Event(AddItem(item),currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Paid-Adding Item: $item",currentCart))
      goto(Shopping) applying ItemAdded(item) forMax(1 second)
  }

  whenUnhandled {
    case Event(SaveSnapshotSuccess(metadata),currentCart) =>
      println("111111111111"+currentCart)
      println("0000000000000000"+metadata)
      context.system.eventStream.publish(CurrentCart("Successfully saved snapshot",currentCart))
      //假如不需要保存历史购物过程，可以清理日志和旧快照
      deleteSnapshots(SnapshotSelectionCriteria(maxSequenceNr = metadata.sequenceNr - 1))
      deleteMessages(metadata.sequenceNr)
      stay()
    case Event(SaveSnapshotFailure(metadata, reason),currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Fail to save snapshot for $reason",currentCart))
      stay()
    case Event(DeleteMessagesSuccess(toSeq),currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Succefully deleted journal upto: $toSeq",currentCart))
      stay()
    case Event(DeleteMessagesFailure(cause,toSeq),currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Failed to delete journal upto: $toSeq because: $cause",currentCart))
      stay()
    case Event(DeleteSnapshotsSuccess(crit),currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Successfully deleted snapshots for $crit",currentCart))
      stay()
    case Event(DeleteSnapshotsFailure(crit,cause),currentCart) =>
      context.system.eventStream.publish(CurrentCart(s"Failed to delete snapshots $crit because: $cause",currentCart))
      stay()
    case _ => goto(LookingAround)
  }

  onTransition {
    case LookingAround -> Shopping =>
      context.system.eventStream.publish(CurrentCart("State changed: LookingAround -> Shopping",stateData))
    case Shopping -> Inactive =>
      context.system.eventStream.publish(CurrentCart("State changed: Shopping -> Inactive",stateData))
    case Shopping -> Paid =>
      context.system.eventStream.publish(CurrentCart("State changed: Shopping -> Paid",stateData))
    case Inactive -> Shopping =>
      context.system.eventStream.publish(CurrentCart("State changed: Inactive -> Shopping",stateData))
    case Paid -> LookingAround =>
      context.system.eventStream.publish(CurrentCart("State changed: Paid -> LookingAround",stateData))
  }
  override def onRecoveryCompleted(): Unit = {
    context.system.eventStream.publish(CurrentCart("OnRecoveryCompleted", stateData))
  }

  override def onPersistFailure(cause: Throwable, event: Any, seqNr: Long): Unit =
    context.system.eventStream.publish(CurrentCart(s"onPersistFailure ${cause.getMessage}",stateData))

  override def onPersistRejected(cause: Throwable, event: Any, seqNr: Long): Unit =
    context.system.eventStream.publish(CurrentCart(s"onPersistRejected ${cause.getMessage}",stateData))

  override def onRecoveryFailure(cause: Throwable, event: Option[Any]): Unit =
    context.system.eventStream.publish(CurrentCart(s"onRecoveryFailure ${cause.getMessage}",stateData))


}
